package edu.pku.sei.mte.modelmatch;

import static choco.Choco.eq;
import static choco.Choco.makeIntVar;
import static choco.Choco.neq;
import static choco.Choco.or;
import static choco.Choco.relationPairAC;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import choco.cp.model.CPModel;
import choco.kernel.model.Model;
import choco.kernel.model.constraints.Constraint;
import choco.kernel.model.variables.integer.IntegerVariable;
import edu.pku.sei.mte.mtemodel.metamodel.RTypeClass;
import edu.pku.sei.mte.mtemodel.metamodel.RTypeGraph;
import edu.pku.sei.mte.mtemodel.model.MClass;
import edu.pku.sei.mte.mtemodel.model.MReference;
import edu.pku.sei.mte.mtemodel.mtmodel.common.Variable;
import edu.pku.sei.mte.mtemodel.mtmodel.pattern.TPatternGraph;
import edu.pku.sei.mte.runtime.Context;

public class MMChocoModel {

	private TPatternGraph pattern;
	private ChocoMModelGraph cmodel;
	
	public int PNC;
	public int PEC;
	
	public IntegerVariable[] PN;
	public IntegerVariable[] PNType;
	public IntegerVariable[] PE;
	public IntegerVariable[] PEType;
	public IntegerVariable[] PES;
	public IntegerVariable[] PET;
	public IntegerVariable[] PESO;
	public IntegerVariable[] PETO;
	public IntegerVariable noOrder;
    
	public List<Constraint> constraints = new LinkedList<Constraint>();
	
	public MMChocoModel(TPatternGraph pattern, ChocoMModelGraph cmodel){
		this.pattern = pattern;
		this.cmodel = cmodel;
		PNC = pattern.getNodes().size();
		PEC = pattern.getEdges().size();
		if(cmodel.nodeCount < PNC || cmodel.edgeCount < PEC)
			return;
		
		RTypeGraph typeGraph = cmodel.model.getTypeGraph();
		//model:
		PN = new IntegerVariable[PNC];
		PNType = new IntegerVariable[PNC];
        for (int i = 0; i < PNC; i++) {
            PN[i] = makeIntVar("PN" + i, 0, cmodel.nodeCount-1, "cp:decision");
            List<Integer> typeArray = new ArrayList<Integer>();
            List<RTypeClass> nl = cmodel.model.getTypeGraph().getNodes();
            RTypeClass type = pattern.getNodes().get(i).getType();
            for(int j = 0; j < nl.size(); j++){
            	if(nl.get(j).isA(type))
            		typeArray.add(j+1);
            }
			PNType[i] = makeIntVar("PNType" + i, typeArray, "cp:enum");
        }
        PE = new IntegerVariable[PEC];
        PEType = new IntegerVariable[PEC];
        PES = new IntegerVariable[PEC];
        PET = new IntegerVariable[PEC];
        PESO = new IntegerVariable[PEC];
        PETO = new IntegerVariable[PEC];
        noOrder = makeIntVar("noorder", -1, 0, "cp:no_decision");
        for (int i = 0; i < PEC; i++) {
        	PE[i] = makeIntVar("PE" + i, 0, cmodel.edgeCount-1, "cp:decision");
            PEType[i] = makeIntVar("PEType" + i, 0, typeGraph.getEdges().size(), "cp:no_decision");
            PES[i] = makeIntVar("PES" + i, 0, cmodel.nodeCount-1, "cp:no_decision");
            PET[i] = makeIntVar("PET" + i, 0, cmodel.nodeCount-1, "cp:no_decision");
            PESO[i] = makeIntVar("PESO" + i, -1, cmodel.nodeCount-1, "cp:no_decision");
            PETO[i] = makeIntVar("PETO" + i, -1, cmodel.nodeCount-1, "cp:no_decision");
        }
        //constraints:
        constraints.add(eq(noOrder, -1));
        for (int i = 0; i < PNC; i++)
        	for(int j = i+1; j<PNC; j++)
        		constraints.add(neq(PN[i], PN[j]));
        for (int i = 0; i < PEC; i++)
        	for(int j = i+1; j<PEC; j++)
        		constraints.add(neq(PE[i], PE[j]));
	}
	
	public Model getChocoModel(Context base){
		if(cmodel.nodeCount < PNC || cmodel.edgeCount < PEC)
			return null;
		for(int i=0; i<cmodel.nodeCount; i++){
			if(base.checkModelBindingExistIn(cmodel.model.getNodes().get(i), cmodel.model)){
				if(cmodel.nodeTypeArray[i] > 0)
					cmodel.nodeTypeArray[i] = -cmodel.nodeTypeArray[i];
			}else if(cmodel.nodeTypeArray[i] < 0)
				cmodel.nodeTypeArray[i] = -cmodel.nodeTypeArray[i];
		}
		for(int i=0; i<cmodel.edgeCount; i++){
			if(base.checkModelBindingExistIn(cmodel.model.getEdges().get(i), cmodel.model)){
				if(cmodel.edgeTypeArray[i] > 0)
					cmodel.edgeTypeArray[i] = -cmodel.edgeTypeArray[i];
			}else if(cmodel.edgeTypeArray[i] < 0)
				cmodel.edgeTypeArray[i] = -cmodel.edgeTypeArray[i];
		}
		
		Model m = new CPModel();
		for(Constraint c : constraints){
			m.addConstraint(c);
		}
		RTypeGraph typeGraph = cmodel.model.getTypeGraph();
		for (int i = 0; i < PNC; i++) {
			Variable var = base.getVariable(pattern.getNodes().get(i).getName());
			if(var!=null&&var.getValue()!=Variable.UNINITIATED_VALUE){
				MClass mc = (MClass) var.getValue();
				m.addConstraint(eq(PN[i], cmodel.model.getNodes().indexOf(mc)));
				m.addConstraint(eq(PNType[i], typeGraph.getNodes().indexOf(mc.getType())+1));
			} else {
				m.addConstraint(relationPairAC(PN[i], PNType[i], cmodel.nodeTypeArrayEqual));
			}
        }
        for (int i = 0; i < PEC; i++) {
        	Variable var = base.getVariable(pattern.getEdges().get(i).getName());
			if(var!=null&&var.getValue()!=Variable.UNINITIATED_VALUE){
				MReference mr = (MReference) var.getValue();
				m.addConstraint(eq(PE[i], cmodel.model.getEdges().indexOf(mr)));
				m.addConstraint(eq(PEType[i], typeGraph.getEdges().indexOf(mr.getType())+1));
				m.addConstraint(eq(PES[i], cmodel.model.getNodes().indexOf(mr.getSource())));
				m.addConstraint(eq(PET[i], cmodel.model.getNodes().indexOf(mr.getTarget())));
				m.addConstraint(eq(PESO[i], cmodel.model.getEdges().get(i).getSourceOrder()));
				m.addConstraint(eq(PETO[i], cmodel.model.getEdges().get(i).getTargetOrder()));
			} else {
				m.addConstraint(relationPairAC(PE[i], PEType[i], cmodel.edgeTypeArrayEqual));
				m.addConstraint(relationPairAC(PE[i], PES[i], cmodel.edgeSourceArrayEqual));
				m.addConstraint(relationPairAC(PE[i], PET[i], cmodel.edgeTargetArrayEqual));
				m.addConstraint(relationPairAC(PE[i], PESO[i], cmodel.edgeSourceOrderArrayEqual));
				m.addConstraint(relationPairAC(PE[i], PETO[i], cmodel.edgeTargetOrderArrayEqual));
				m.addConstraint(eq(PEType[i], typeGraph.getEdges().indexOf(pattern.getEdges().get(i).getType())+1));
				m.addConstraint(eq(PES[i], PN[pattern.getNodes().indexOf(pattern.getEdges().get(i).getSource())]));
				m.addConstraint(eq(PET[i], PN[pattern.getNodes().indexOf(pattern.getEdges().get(i).getTarget())]));
				m.addConstraint(or(eq(noOrder, pattern.getEdges().get(i).getSourceOrder()), eq(PESO[i], pattern.getEdges().get(i).getSourceOrder())));
				m.addConstraint(or(eq(noOrder, pattern.getEdges().get(i).getTargetOrder()), eq(PETO[i], pattern.getEdges().get(i).getTargetOrder())));
        	}
        }
		return m;
	}
}
